package com.dt.shirodemo.config;

import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.realm.AuthenticatingRealm;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.subject.PrincipalCollection;

import java.util.HashSet;
import java.util.Set;

/**
 * 自定义 Realm 永远完成具体的认证和授权操作
 * realm 的父类抽象类
 * AuthenticatingRealm 只负责认证（登录） 的 Realm 父类
 * AuthorizingRealm    负责认证(登录) 和授权 的 Realm 父类
 */
public class MyRealm extends AuthorizingRealm {

    /**
     * Shiro 的认证方法，我们需要在这个方法中获取用户的信息（从数据库中）
     *
     * @param authenticationToken 用户登录时的Token，这个对象中将存放着我们用户在浏览器中存放的账号和密码
     * @return 返回一个 AuthenticationInfo 对象，这个放回以后Shiro 会调用这个对象中的一些方法来完成对密码的验证，
     * 密码是由Shiro进行验证合法
     * @throws AuthenticationException 如果认证失败 Shiro 就会抛出AuthenticationException
     *                                 我们也可以手动自己跑出这个 AuthenticationException以及他的任意子异常类，不同的异常类型可以认证过程中的不同错误情况，
     *                                 我们需要根据异常类型来为用户返回特定的响应数据
     *                                 AuthenticationException 异常的子类 可以我们自己抛出
     *                                 AccountException 账号异常 可以我们自己抛出
     *                                 UnknowAccountException 账号不存在异常，可以我们自己抛出
     *                                 LockedAccountException 账号异常锁定异常 可以我们自己抛出
     *                                 IncorrectCredentialsException 密码错误异常 这个异常会在Shiro进行密码验证时抛出
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //将AuthenticationToknen 强转成 UsernamePasswordToken 这样获取账号和密码更加方便
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        //获取用户在浏览器中输入的账号
        String username = token.getUsername();
        //认证账号，正常情况我们这里需要凑够数据库中获取账号的信息，以及其他关键数据，例如账号是否被冻结等等
        String dbusername = username;
        //判断用户账号密码是否正确
        if (!"admin".equals(dbusername) && !"zhangsan".equals(dbusername)) {
            throw new UnknownAccountException("账号不存在");
        }
        // if ("zhangsan".equals(username)) {
        //     throw new LockedAccountException("账号被锁定");
        // }

        //定义一个密码，这个密码应该来自数据库中
        String dbpassword = "123456";

        //为密码加密
        //参数1 为加密算法 我们选择MD5加密
        //参数2 被加密的数据
        //参数3 为加密时的盐值，用于改变加密后数据结果
        //      通常这个盐值需要选择一个表中唯一的数据，例如表中的账号
        //参数4 加密次数
        Object obj = new SimpleHash("MD5", dbpassword, "", 1);
        System.out.println(obj.toString());
        //认证密码是否正确 使用加密后的密码登录
        return new SimpleAuthenticationInfo(dbusername, obj.toString(), getName());

        /*
        注意：
            1、 通 常 数 据 库 中 存 放的 数 据 不应该是 明码 123456 而 是 加密 后 的 数 据例 如
            e10adc3949ba59abbe56e057f20f883e，这是使用 MD5 加密后的 123456，如果数据
            库中的密码已经是加密后的那么这里可以不选择进行加密。
            2、 如果数据库中的密码已经加密那么页面中传递数据前必须要对密码进行加密才能
            传递，否则无法可能会登录失败。
            3、 如果选择加密传递那么页面和数据库中的密码加密次数以及盐必须相同，否则登录
            一定失败
         */

    }

    //Shiro 用户授权回调方法
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //从Shiro 中获取用户名
        Object userName = principalCollection.getPrimaryPrincipal();
        //创建一个simpleAuthorizationInfo 类的对象，利用这个对象需要设置当前用户的权限信息
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        //创建角色信息的集合
        Set<String> roles = new HashSet<>();
        //这里应该根据账号到数据库中获取用户的所对应的所有角色信息并初始化到roles集合中
        if ("admin".equals(userName)){
            roles.add("admin");
            roles.add("user");
        }else if ("zhangsan".equals(userName)){
            roles.add("user");
        }
        Set<String> psermission = new HashSet<>();

        if ("admin".equals(userName)){
            psermission.add("admin:add");
        }

        //设角色信息
        simpleAuthorizationInfo.setRoles(roles);
        simpleAuthorizationInfo.setStringPermissions(psermission);

        return simpleAuthorizationInfo;
    }
}
